package demoMod.anm2editor.ui;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import demoMod.anm2editor.fonts.FontKeys;
import demoMod.anm2editor.model.Event;
import demoMod.anm2editor.model.KeyFrame;
import demoMod.anm2editor.model.Project;
import demoMod.anm2editor.model.Track;
import demoMod.gdxform.abstracts.Container;
import demoMod.gdxform.core.FormManager;
import demoMod.gdxform.helpers.FontHelper;
import demoMod.gdxform.interfaces.Element;
import demoMod.gdxform.interfaces.KeyboardEventSubscriber;
import demoMod.gdxform.interfaces.MouseEventSubscriber;
import demoMod.gdxform.ui.GHScrollBar;
import demoMod.gdxform.ui.GList;
import demoMod.gdxform.ui.GScrollPane;
import demoMod.gdxform.ui.GVScrollBar;

import java.util.ArrayList;
import java.util.List;

public class Timeline extends Container<Element> implements MouseEventSubscriber, KeyboardEventSubscriber {
    public static final String ID = "Timeline";
    private final float cellWidth;
    private float trackHeight;
    private final GScrollPane scrollPane;
    private final Texture cellTexture;
    private final TrackContentList trackContentList = new TrackContentList();
    private boolean mouseDown = false;
    private boolean draggingAnmLengthMark = false;
    private int pointerIndex = 0;
    private Texture pointer;
    private final Texture animationLengthTexture;
    private final Texture animationLengthMark;
    private boolean playing = false;
    private float playTimer = 0.0F;

    public Timeline() {
        cellWidth = Math.max(16.0F, Gdx.graphics.getWidth() * 0.01F);
        trackHeight = Math.max(20.0F, Gdx.graphics.getHeight() * 0.022F);
        setWidth(Gdx.graphics.getWidth() * 0.6F);
        setHeight(Gdx.graphics.getHeight() * 0.2F);
        setX(0.2F * Gdx.graphics.getWidth());
        setY(20);
        setBackground(Color.WHITE.cpy());
        setBackground(Color.GRAY.cpy());
        setId(ID);
        scrollPane = new GScrollPane(0, 0, getWidth(), getHeight() - 20.0F, getWidth() * 2, getHeight() * 2);
        scrollPane.setBackground(Color.WHITE.cpy());
        scrollPane.setBackground(Color.DARK_GRAY.cpy());
        GHScrollBar hScrollBar = new GHScrollBar(0, 0, getWidth(), 10);
        GVScrollBar vScrollBar = new GVScrollBar(0, 0, 10, getHeight());
        scrollPane.setHScrollBar(hScrollBar);
        scrollPane.setVScrollBar(vScrollBar);
        addElement(scrollPane);
        trackContentList.setCellHeight(trackHeight);
        trackContentList.setMaxSelectedItems(1);
        scrollPane.addElement(trackContentList);
        Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
        pixmap.setColor(Color.WHITE.cpy());
        pixmap.fill();

        Pixmap animationLength = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
        animationLength.setColor(Color.TEAL.cpy());
        animationLength.fill();
        this.animationLengthTexture = new Texture(animationLength);
        this.animationLengthMark = new Texture("anmLengthMark.png");

        cellTexture = new Texture(pixmap);
        setBorderWidth(1);
        setBorder(Color.LIGHT_GRAY.cpy());
        this.pointer = new Texture("pointer.png");
    }

    public TrackContentList getTrackContentList() {
        return trackContentList;
    }

    public void addTrack(String animationName, Track track) {
        TrackContentItem trackContentItem = new TrackContentItem(animationName, track);
        trackContentItem.setId(Integer.toString(track.getId()));
        trackContentList.addItem(trackContentItem);
        int len = StatusBar.getMaxAnimationLength();
        if (len > 120) {
            while (scrollPane.getPlainWidth() < len * getCellWidth()) {
                scrollPane.setPlainWidth(scrollPane.getPlainWidth() + 50 * cellWidth);
            }
        }
    }

    public void addTrack(String animationName, Track track, int index) {
        TrackContentItem trackContentItem = new TrackContentItem(animationName, track);
        trackContentItem.setId(Integer.toString(track.getId()));
        trackContentList.addItem(index, trackContentItem);
        int len = StatusBar.getMaxAnimationLength();
        if (len > 120) {
            while (scrollPane.getPlainWidth() < len * getCellWidth()) {
                scrollPane.setPlainWidth(scrollPane.getPlainWidth() + 50 * cellWidth);
            }
        }
    }

    public void removeTrack(Track track) {
        for (GList.ListCellRenderer<TrackContentItem> element : trackContentList.getElements()) {
            if (element.getElements().get(0).getTrack() == track) {
                trackContentList.removeElement(element);
                break;
            }
        }
    }

    public void clearTrack() {
        for (Element element : trackContentList.getElements()) {
            element.dispose();
        }
        trackContentList.clear();
        scrollPane.getVScrollBar().setProgress(0);
        scrollPane.getHScrollBar().setProgress(0);
    }

    public int getPointerIndex() {
        return pointerIndex;
    }

    public void setPointerIndex(int pointerIndex) {
        if (Project.currentProject.getCurrentAnimation() == null) {
            this.pointerIndex = 0;
            return;
        }
        this.pointerIndex = pointerIndex % Project.currentProject.getCurrentAnimation().getAnimationLength();
        if (this.pointerIndex < 0) {
            this.pointerIndex = pointerIndex % Project.currentProject.getCurrentAnimation().getAnimationLength() - 1;
        }
    }

    public KeyFrame getCurrentSelectedKeyFrame() {
        KeyFrame ret = null;
        for (int i=0;i<trackContentList.getElements().size();i++) {
            TrackContentItem item = trackContentList.getItem(i);
            if (item.getCurrentSelectedKeyFrame() != null) {
                ret = item.getCurrentSelectedKeyFrame();
            }
        }
        return ret;
    }

    public Track getCurrentSelectedTrack() {
        KeyFrame keyFrame = getCurrentSelectedKeyFrame();
        if (keyFrame != null) {
            for (GList.ListCellRenderer<TrackContentItem> element : trackContentList.getElements()) {
                if (element.getElements().get(0).getTrack().getContent(Project.currentProject.getCurrentAnimation().getName()).getKeyFrames().contains(keyFrame)) {
                    return element.getElements().get(0).getTrack();
                }
            }
        }
        return null;
    }

    public void setPointer(Texture pointer) {
        if (this.pointer != null) {
            this.pointer.dispose();
        }
        this.pointer = pointer;
    }

    public void play() {
        this.playing = true;
    }

    public void pause() {
        this.playing = false;
    }

    public boolean isPlaying() {
        return playing;
    }

    public void deselect() {
        KeyFrame keyFrame = getCurrentSelectedKeyFrame();
        if (keyFrame != null) {
            keyFrame.selected = false;
        }
        List<GList.ListCellRenderer<TrackContentItem>> elements = trackContentList.getElements();
        int index = 0;
        for (GList.ListCellRenderer<TrackContentItem> element : elements) {
            if (element.isSelected()) {
                trackContentList.setSelected(index, false);
            }
            index++;
        }
    }

    public void selectTrack(int index) {
        deselect();
        trackContentList.setSelected(index, true);
    }

    public void swapTrack(int index1, int index2) {
        this.trackContentList.swap(index1, index2);
    }

    @Override
    public void update() {
        super.update();
        if (this.playing) {
            this.playTimer += Gdx.graphics.getDeltaTime();
            if (this.playTimer >= 1.0F / Project.currentProject.getFps()) {
                this.setPointerIndex(pointerIndex + 1);
                this.playTimer -= 1.0F / Project.currentProject.getFps();
            }
            float currX = (getWidth() - scrollPane.getPlainWidth()) * scrollPane.getHScrollBar().getProgress();
            float percent = getWidth() / scrollPane.getPlainWidth();
            if (getX(true) + currX + cellWidth * pointerIndex > getX(true) + getWidth()) {
                scrollPane.getHScrollBar().setProgress(scrollPane.getHScrollBar().getProgress() + percent);
            } else if (getX(true) + currX + cellWidth * pointerIndex < getX(true)) {
                scrollPane.getHScrollBar().setProgress(0);
            }
        }
    }

    @Override
    public void render(SpriteBatch sb) {
        if (!this.visible()) return;
        getFrameBuffer().begin();
        sb.setColor(Color.WHITE);
        Gdx.gl20.glClearColor(0.5F, 0.5F, 0.5F, 1);
        Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);

        if (this.borderTexture != null) {
            sb.draw(this.borderTexture, getX(true) - getBorderWidth(), getY(true) - getBorderWidth(), getWidth() + getBorderWidth() * 2, getHeight() + getBorderWidth() * 2);
        }
        if (this.bgTexture != null) {
            sb.draw(this.bgTexture, getX(true), getY(true), getWidth(), getHeight());
        }
        float currX = (getWidth() - scrollPane.getPlainWidth()) * scrollPane.getHScrollBar().getProgress();
        if (Project.currentProject.getCurrentAnimation() != null) {
            sb.draw(this.animationLengthTexture, getX(true) + currX, getY(true), Project.currentProject.getCurrentAnimation().getAnimationLength() * cellWidth, getHeight());
        }
        sb.setColor(Color.GOLD);
        for (Event event : Project.currentProject.getEvents()) {
            List<Integer> usages = event.getUsages().getOrDefault(Project.currentProject.getCurrentAnimation().getName(), new ArrayList<>());
            for (int i : usages) {
                sb.draw(this.cellTexture, currX + getX(true) + i * cellWidth, getY(true),
                        cellWidth, getHeight());
            }
        }
        sb.setColor(Color.WHITE);
        sb.flush();
        getFrameBuffer().end();

        for (Element element : getElements()) {
            if (!(element instanceof Container)) {
                getFrameBuffer().begin();
            }
            element.render(sb);
            if (!(element instanceof Container)) {
                getFrameBuffer().end();
            }
        }

        getFrameBuffer().begin();
        int index = 0;
        FontHelper.getFont(FontKeys.SIM_HEI_12).setColor(Color.DARK_GRAY);
        while (currX < getWidth()) {
            float lineHeight = index % 5 == 0 ? 12.0F : 6.0F;
            sb.draw(this.cellTexture, getX(true) + currX, getY(true) + getHeight() - lineHeight, 1, lineHeight);
            if (index % 5 == 0) {
                FontHelper.getFont(FontKeys.SIM_HEI_12).draw(sb, Integer.toString(index), getX(true) + currX, getY(true) + getHeight() - lineHeight);
            }
            index++;
            currX += cellWidth;
        }

        currX = (getWidth() - scrollPane.getPlainWidth()) * scrollPane.getHScrollBar().getProgress();
        if (Project.currentProject.getCurrentAnimation() != null) {
            sb.draw(this.animationLengthMark,
                    getX(true) + currX + (Project.currentProject.getCurrentAnimation().getAnimationLength() - 0.5F) * cellWidth,
                    getY(true) + getHeight() - this.pointer.getHeight() + 20,
                    cellWidth, trackHeight);
        }
        sb.setColor(Color.BLACK);
        sb.draw(this.cellTexture, currX + getX(true) + (pointerIndex + 0.5F) * cellWidth - 1,
                getY(true), 2, getHeight());
        sb.setColor(Color.WHITE);
        sb.draw(this.pointer, currX + getX(true) + pointerIndex * cellWidth, getY(true) + getHeight() - this.pointer.getHeight() + 20, cellWidth, trackHeight);
        sb.flush();
        getFrameBuffer().end();

        Texture texture = getFrameBuffer().getColorBufferTexture();
        sb.setColor(Color.WHITE);
        sb.draw(texture,
                (int) getX(true) - (int) this.getBorderWidth(), (int) getY(true) - (int) this.getBorderWidth(),
                0, 0,
                (int) getWidth() + (int) this.getBorderWidth() * 2, (int) getHeight() + (int) this.getBorderWidth() * 2,
                1, 1,
                0,
                (int) getX(true) - (int) this.getBorderWidth(), (int) getY(true) - (int) this.getBorderWidth(),
                (int) getWidth() + (int) this.getBorderWidth() * 2, (int) getHeight() + (int) this.getBorderWidth() * 2,
                false, true);
        sb.flush();
    }

    public float getCellWidth() {
        return cellWidth;
    }

    public float getTrackHeight() {
        return trackHeight;
    }

    public void setTrackHeight(float trackHeight) {
        this.trackHeight = trackHeight;
    }

    public GScrollPane getScrollPane() {
        return scrollPane;
    }

    @Override
    public boolean keyDown(int keyCode) {
        if (keyCode == Input.Keys.SPACE) {
            this.playing = !this.playing;
            return true;
        }
        return false;
    }

    @Override
    public boolean keyUp(int keyCode) {
        return false;
    }

    @Override
    public boolean keyTyped(char key) {
        return false;
    }

    @Override
    public boolean clickDown(int screenX, int screenY, int button) {
        mouseDown = (float)screenX >= this.getX(true) && (float)screenX <= this.getX(true) + this.getWidth() && (float)(Gdx.graphics.getHeight() - screenY) >= this.getY(true) && (float)(Gdx.graphics.getHeight() - screenY) <= this.getY(true) + this.getHeight();
        if (mouseDown) {
            deselect();
            ((TrackPane) FormManager.getInstance().getContainerById(TrackPane.ID)).deselect();
            FormManager.getInstance().moveToTop(this);
            List<Element> elements = this.getElements();
            for(int i = elements.size() - 1; i >= 0; --i) {
                Element element = elements.get(i);
                if (element instanceof MouseEventSubscriber && ((MouseEventSubscriber)element).clickDown(screenX, screenY, button)) {
                    return true;
                }
            }
            float currX = (getWidth() - scrollPane.getPlainWidth()) * scrollPane.getHScrollBar().getProgress();
            int index = 0;
            while (screenX > currX + (index + 1) * cellWidth + getX(true)) {
                index++;
            }
            int animationLength;
            if (Project.currentProject.getCurrentAnimation() != null) {
                animationLength = Project.currentProject.getCurrentAnimation().getAnimationLength();
            } else {
                FormManager.getInstance().getEventHooks().setCurrentFocusedElement(this);
                return true;
            }
            if (index == animationLength - 1 && Math.abs(getX(true) + currX + animationLength * cellWidth - screenX) < cellWidth * 0.5F) {
                draggingAnmLengthMark = true;
            } else {
                setPointerIndex(index);
            }
            FormManager.getInstance().getEventHooks().setCurrentFocusedElement(this);
            return true;
        }
        return false;
    }

    @Override
    public void activate() {
        super.activate();
        this.setBorder(new Color(0.05882F, 0.33725F, 0.98431F, 1));
    }

    @Override
    public void deactivate() {
        super.deactivate();
        this.setBorder(Color.LIGHT_GRAY.cpy());
    }

    @Override
    public boolean clickUp(int screenX, int screenY, int button) {
        if (!this.mouseDown) {
            return false;
        } else {
            this.mouseDown = false;
            this.draggingAnmLengthMark = false;
            List<Element> elements = this.getElements();

            for(int i = elements.size() - 1; i >= 0; --i) {
                Element element = elements.get(i);
                if (element instanceof MouseEventSubscriber && ((MouseEventSubscriber)element).clickUp(screenX, screenY, button)) {
                    break;
                }
            }
            return true;
        }
    }

    @Override
    public boolean mouseDragged(int screenX, int screenY) {
        List<Element> elements = getElements();
        for (int i=elements.size() - 1;i >= 0;i--) {
            Element element = elements.get(i);
            if (element instanceof MouseEventSubscriber) {
                if (((MouseEventSubscriber) element).mouseDragged(screenX, screenY)) {
                    ((TrackPane)FormManager.getInstance().getContainerById(TrackPane.ID)).getScrollPane().getVScrollBar().setProgress(scrollPane.getVScrollBar().getProgress());
                    return true;
                }
            }
            if (mouseDown) {
                float currX = (getWidth() - scrollPane.getPlainWidth()) * scrollPane.getHScrollBar().getProgress();
                int index = 0;
                while (screenX > currX + (index + 1) * cellWidth + getX(true)) {
                    index++;
                }
                if (draggingAnmLengthMark) {
                    Project.currentProject.getCurrentAnimation().setAnimationLength(Math.max(1, index));
                } else {
                    setPointerIndex(index);
                }
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean mouseMoved(int x, int y) {
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        if (!this.visible()) return false;
        List<Element> elements = getElements();
        for (int i=elements.size() - 1;i >= 0;i--) {
            Element element = elements.get(i);
            if (element instanceof MouseEventSubscriber) {
                if (((MouseEventSubscriber) element).scrolled(amount)) {
                    ((TrackPane)FormManager.getInstance().getContainerById(TrackPane.ID)).getScrollPane().getVScrollBar().setProgress(scrollPane.getVScrollBar().getProgress());
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean moveToElementBorder(Element element) {
        return false;
    }

    @Override
    public void dispose() {
        super.dispose();
        this.animationLengthTexture.dispose();
        this.cellTexture.dispose();
        this.animationLengthTexture.dispose();
        this.pointer.dispose();
    }

    @Override
    public boolean hasFocus() {
        return true;
    }
}
